﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using PloobsEngine.DataStructure;

namespace PloobsEngine.Utils
{

    //USAGE
    /*
    The full code is below but the usage is straightforward: 
    Create a new InterpolatorCollection wherever you need interpolators. 
    Make sure to call the Update method on the InterpolatorCollection each frame. 
    Use the Create method on the collection to create new interpolators. 
    */
    // interpolates a color from Black to White, and calls DoStuff() when the interpolation is complete
    // interpolatorCollection.Create(0, 1, i => color = Color.Lerp(Color.Black, Color.White, i), s => DoStuff());

    /// <summary>
    /// A managed collection of interpolators.
    /// </summary>
    public sealed class InterpolatorCollection : IUpdateable
    {
        public InterpolatorCollection(EngineStuff engine)
            : base(engine)
        {
            this.Start();
        }
                

        private readonly Pool<Interpolator> interpolators = new Pool<Interpolator>(10, true, i => i.IsActive, () => new Interpolator());

        /// <summary>
        /// Creates a new Interpolator.
        /// </summary>
        /// <param name="start">The starting value.</param>
        /// <param name="end">The ending value.</param>
        /// <param name="step">An optional callback to invoke when the Interpolator is updated.</param>
        /// <param name="completed">An optional callback to invoke when the Interpolator completes.</param>
        /// <returns>The Interpolator instance.</returns>
        public Interpolator NewInterpolator(
          float start,
          float end,
          Action<Interpolator> step,
          Action<Interpolator> completed)
        {
            return NewInterpolator(start, end, 1f, InterpolatorScales.Linear, step, completed);
        }

        /// <summary>
        /// Creates a new Interpolator.
        /// </summary>
        /// <param name="start">The starting value.</param>
        /// <param name="end">The ending value.</param>
        /// <param name="length">The length of time, in seconds, to perform the interpolation.</param>
        /// <param name="step">An optional callback to invoke when the Interpolator is updated.</param>
        /// <param name="completed">An optional callback to invoke when the Interpolator completes.</param>
        /// <returns>The Interpolator instance.</returns>
        public Interpolator NewInterpolator(
          float start,
          float end,
          float length,
          Action<Interpolator> step,
          Action<Interpolator> completed)
        {
            return NewInterpolator(start, end, length, InterpolatorScales.Linear, step, completed);
        }

        /// <summary>
        /// Creates a new Interpolator.
        /// </summary>
        /// <param name="start">The starting value.</param>
        /// <param name="end">The ending value.</param>
        /// <param name="length">The length of time, in seconds, to perform the interpolation.</param>
        /// <param name="scale">A method to perform</param>
        /// <param name="step">An optional callback to invoke when the Interpolator is updated.</param>
        /// <param name="completed">An optional callback to invoke when the Interpolator completes.</param>
        /// <returns>The Interpolator instance.</returns>
        public Interpolator NewInterpolator(
          float start,
          float end,
          float length,
          InterpolatorScaleDelegate scale,
          Action<Interpolator> step,
          Action<Interpolator> completed)
        {
            lock (interpolators)
            {
                Interpolator i = interpolators.New();
                i.Reset(start, end, length, scale, step, completed);

                return i;
            }
        }

        /// <summary>
        /// Updates all active Interpolators in the collection.
        /// </summary>
        /// <param name="gameTime">The current game timestamp.</param>
        public void Update(GameTime gameTime)
        {
            lock (interpolators)
            {
                for (int i = 0; i < interpolators.ValidCount; i++)
                    interpolators[i].Update(gameTime);
                interpolators.CleanUp();
            }
        }
    }

    /// <summary>
    /// A delegate used by Interpolators to scale their progress and generate their current value.
    /// </summary>
    /// <param name="progress">The current progress of the Interpolator in the range [0, 1].</param>
    /// <returns>A value representing the scaled progress used to generate the Interpolator's Value.</returns>
    public delegate float InterpolatorScaleDelegate(float progress);

    public sealed class Interpolator
    {
        private bool valid;
        private float progress;
        private float start;
        private float end;
        private float range;
        private float speed;
        private float value;
        private InterpolatorScaleDelegate scale;
        private Action<Interpolator> step;
        private Action<Interpolator> completed;

        /// <summary>
        /// Gets whether or not the interpolator is active.
        /// </summary>
        public bool IsActive { get { return valid; } }

        /// <summary>
        /// Gets the interpolator's progress in the range of [0, 1].
        /// </summary>
        public float Progress { get { return progress; } }

        /// <summary>
        /// Gets the interpolator's starting value.
        /// </summary>
        public float Start { get { return start; } }

        /// <summary>
        /// Gets the interpolator's ending value.
        /// </summary>
        public float End { get { return end; } }

        /// <summary>
        /// Gets the interpolator's current value.
        /// </summary>
        public float Value { get { return value; } }

        /// <summary>
        /// Gets or sets some extra data to the timer.
        /// </summary>
        public object Tag { get; set; }

        /// <summary>
        /// Internal constructor used by InterpolatorCollection
        /// </summary>
        internal Interpolator() { }

        /// <summary>
        /// Creates a new Interpolator.
        /// </summary>
        /// <param name="startValue">The starting value.</param>
        /// <param name="endValue">The ending value.</param>
        /// <param name="step">An optional delegate to invoke each update.</param>
        /// <param name="completed">An optional delegate to invoke upon completion.</param>
        public Interpolator(float startValue, float endValue, Action<Interpolator> step, Action<Interpolator> completed)
            : this(startValue, endValue, 1f, InterpolatorScales.Linear, step, completed)
        {
        }

        /// <summary>
        /// Creates a new Interpolator.
        /// </summary>
        /// <param name="startValue">The starting value.</param>
        /// <param name="endValue">The ending value.</param>
        /// <param name="interpolationLength">The amount of time, in seconds, for the interpolation to occur.</param>
        /// <param name="step">An optional delegate to invoke each update.</param>
        /// <param name="completed">An optional delegate to invoke upon completion.</param>
        public Interpolator(float startValue, float endValue, float interpolationLength, Action<Interpolator> step, Action<Interpolator> completed)
            : this(startValue, endValue, interpolationLength, InterpolatorScales.Linear, step, completed)
        {
        }

        /// <summary>
        /// Creates a new Interpolator.
        /// </summary>
        /// <param name="startValue">The starting value.</param>
        /// <param name="endValue">The ending value.</param>
        /// <param name="interpolationLength">The amount of time, in seconds, for the interpolation to occur.</param>
        /// <param name="scale">A custom delegate to use for scaling the Interpolator's value.</param>
        /// <param name="step">An optional delegate to invoke each update.</param>
        /// <param name="completed">An optional delegate to invoke upon completion.</param>
        public Interpolator(float startValue, float endValue, float interpolationLength, InterpolatorScaleDelegate scale, Action<Interpolator> step, Action<Interpolator> completed)
        {
            Reset(startValue, endValue, interpolationLength, scale, step, completed);
        }

        /// <summary>
        /// Stops the Interpolator.
        /// </summary>
        public void Stop()
        {
            valid = false;
        }

        /// <summary>
        /// Forces the interpolator to set itself to its final position and fire off its delegates before invalidating itself.
        /// </summary>
        public void ForceFinish()
        {
            if (valid)
            {
                valid = false;
                progress = 1;
                float scaledProgress = scale(progress);
                value = start + range * scaledProgress;

                if (step != null)
                    step(this);

                if (completed != null)
                    completed(this);
            }
        }

        internal void Reset(float s, float e, float l, InterpolatorScaleDelegate scaleFunc, Action<Interpolator> stepFunc, Action<Interpolator> completedFunc)
        {
            if (l <= 0f)
                throw new ArgumentException("length must be greater than zero");

            if (scaleFunc == null)
                throw new ArgumentNullException("scaleFunc");

            valid = true;
            progress = 0f;

            start = s;
            end = e;
            range = e - s;
            speed = 1f / l;

            scale = scaleFunc;
            step = stepFunc;
            completed = completedFunc;
        }

        /// <summary>
        /// Updates the Interpolator.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            if (!valid)
                return;

            // update the progress, clamping at 1f
            progress = Math.Min(progress + speed * (float)gameTime.ElapsedGameTime.TotalSeconds, 1f);

            // get the scaled progress and use that to generate the value
            float scaledProgress = scale(progress);
            value = start + range * scaledProgress;

            // invoke the step callback
            if (step != null)
                step(this);

            // if the progress is 1...
            if (progress == 1f)
            {
                // the interpolator is done
                valid = false;

                // invoke the completed callback
                if (completed != null)
                    completed(this);

                Tag = null;
                scale = null;
                step = null;
                completed = null;
            }
        }
    }


    /// <summary>
    /// A static class that contains predefined scales for Interpolators.
    /// </summary>
    public static class InterpolatorScales
    {
        /// <summary>
        /// A linear interpolator scale. This is used by default by the Interpolator if no other scale is given.
        /// </summary>
        public static readonly InterpolatorScaleDelegate Linear = LinearInterpolation;

        /// <summary>
        /// A quadratic interpolator scale.
        /// </summary>
        public static readonly InterpolatorScaleDelegate Quadratic = QuadraticInterpolation;

        /// <summary>
        /// A cubic interpolator scale.
        /// </summary>
        public static readonly InterpolatorScaleDelegate Cubic = CubicInterpolation;

        /// <summary>
        /// A quartic interpolator scale.
        /// </summary>
        public static readonly InterpolatorScaleDelegate Quartic = QuarticInterpolation;

        private static float LinearInterpolation(float progress)
        {
            return progress;
        }

        private static float QuadraticInterpolation(float progress)
        {
            return progress * progress;
        }

        private static float CubicInterpolation(float progress)
        {
            return progress * progress * progress;
        }

        private static float QuarticInterpolation(float progress)
        {
            return progress * progress * progress * progress;
        }
    }




}
